/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#ifndef jsweakmap_h#define jsweakmap_h#include"mozilla/LinkedList.h"#include"mozilla/Move.h"#include"jscompartment.h"#include"jsfriendapi.h"#include"jsobj.h"#include"gc/Marking.h"#include"gc/StoreBuffer.h"#include"js/HashTable.h"namespacejs{classGCMarker;classWeakMapBase;// A subclass template of js::HashMap whose keys and values may be garbage-collected. When// a key is collected, the table entry disappears, dropping its reference to the value.//// More precisely://// A WeakMap entry is live if and only if both the WeakMap and the entry's key// are live. An entry holds a strong reference to its value.//// You must call this table's 'trace' method when its owning object is reached// by the garbage collection tracer. Once a table is known to be live, the// implementation takes care of the special weak marking (ie, marking through// the implicit edges stored in the map) and of removing (sweeping) table// entries when collection is complete.typedefHashSet<WeakMapBase*,DefaultHasher<WeakMapBase*>,SystemAllocPolicy>WeakMapSet;// Common base class for all WeakMap specializations, used for calling// subclasses' GC-related methods.classWeakMapBase:publicmozilla::LinkedListElement<WeakMapBase>{friendclassjs::GCMarker;public:WeakMapBase(JSObject*memOf,JS::Zone*zone);virtual~WeakMapBase();Zone*zone()const{returnzone_;}// Garbage collector entry points.// Unmark all weak maps in a zone.staticvoidunmarkZone(JS::Zone*zone);// Mark all the weakmaps in a zone.staticvoidtraceZone(JS::Zone*zone,JSTracer*tracer);// Check all weak maps in a zone that have been marked as live in this garbage// collection, and mark the values of all entries that have become strong references// to them. Return true if we marked any new values, indicating that we need to make// another pass. In other words, mark my marked maps' marked members' mid-collection.staticboolmarkZoneIteratively(JS::Zone*zone,GCMarker*marker);// Add zone edges for weakmaps with key delegates in a different zone.staticboolfindInterZoneEdges(JS::Zone*zone);// Sweep the weak maps in a zone, removing dead weak maps and removing// entries of live weak maps whose keys are dead.staticvoidsweepZone(JS::Zone*zone);// Trace all delayed weak map bindings. Used by the cycle collector.staticvoidtraceAllMappings(WeakMapTracer*tracer);// Save information about which weak maps are marked for a zone.staticboolsaveZoneMarkedWeakMaps(JS::Zone*zone,WeakMapSet&markedWeakMaps);// Restore information about which weak maps are marked for many zones.staticvoidrestoreMarkedWeakMaps(WeakMapSet&markedWeakMaps);protected:// Instance member functions called by the above. Instantiations of WeakMap override// these with definitions appropriate for their Key and Value types.virtualvoidtrace(JSTracer*tracer)=0;virtualboolfindZoneEdges()=0;virtualvoidsweep()=0;virtualvoidtraceMappings(WeakMapTracer*tracer)=0;virtualvoidfinish()=0;// Any weakmap key types that want to participate in the non-iterative// ephemeron marking must override this method.virtualvoidmarkEntry(GCMarker*marker,gc::Cell*markedCell,JS::GCCellPtrl)=0;virtualboolmarkIteratively(GCMarker*marker)=0;protected:// Object that this weak map is part of, if any.GCPtrObjectmemberOf;// Zone containing this weak map.JS::Zone*zone_;// Whether this object has been traced during garbage collection.boolmarked;};template<typenameT>staticTextractUnbarriered(constWriteBarrieredBase<T>&v){returnv.get();}template<typenameT>staticT*extractUnbarriered(T*v){returnv;}template<classKey,classValue,classHashPolicy=DefaultHasher<Key>>classWeakMap:publicHashMap<Key,Value,HashPolicy,RuntimeAllocPolicy>,publicWeakMapBase{public:typedefHashMap<Key,Value,HashPolicy,RuntimeAllocPolicy>Base;typedeftypenameBase::EnumEnum;typedeftypenameBase::LookupLookup;typedeftypenameBase::EntryEntry;typedeftypenameBase::RangeRange;typedeftypenameBase::PtrPtr;typedeftypenameBase::AddPtrAddPtr;explicitWeakMap(JSContext*cx,JSObject*memOf=nullptr):Base(cx->runtime()),WeakMapBase(memOf,cx->compartment()->zone()){}boolinit(uint32_tlen=16){if(!Base::init(len))returnfalse;zone()->gcWeakMapList().insertFront(this);marked=JS::IsIncrementalGCInProgress(TlsContext.get());returntrue;}// Overwritten to add a read barrier to prevent an incorrectly gray value// from escaping the weak map. See the UnmarkGrayTracer::onChild comment in// gc/Marking.cpp.Ptrlookup(constLookup&l)const{Ptrp=Base::lookup(l);if(p)exposeGCThingToActiveJS(p->value());returnp;}AddPtrlookupForAdd(constLookup&l)const{AddPtrp=Base::lookupForAdd(l);if(p)exposeGCThingToActiveJS(p->value());returnp;}PtrlookupWithDefault(constKey&k,constValue&defaultValue){Ptrp=Base::lookupWithDefault(k,defaultValue);if(p)exposeGCThingToActiveJS(p->value());returnp;}// Resolve ambiguity with LinkedListElement<>::remove.usingBase::remove;// Trace a WeakMap entry based on 'markedCell' getting marked, where// 'origKey' is the key in the weakmap. These will probably be the same,// but can be different eg when markedCell is a delegate for origKey.//// This implementation does not use 'markedCell'; it looks up origKey and// checks the mark bits on everything it cares about, one of which will be// markedCell. But a subclass might use it to optimize the liveness check.voidmarkEntry(GCMarker*marker,gc::Cell*markedCell,JS::GCCellPtrorigKey)override{MOZ_ASSERT(marked);// If this cast fails, then you're instantiating the WeakMap with a// Lookup that can't be constructed from a Cell*. The WeakKeyTable// mechanism is indexed with a GCCellPtr, so that won't work.Ptrp=Base::lookup(static_cast<Lookup>(origKey.asCell()));MOZ_ASSERT(p.found());Keykey(p->key());MOZ_ASSERT((markedCell==extractUnbarriered(key))||(markedCell==getDelegate(key)));if(gc::IsMarked(marker->runtime(),&key)){TraceEdge(marker,&p->value(),"ephemeron value");}elseif(keyNeedsMark(key)){TraceEdge(marker,&p->value(),"WeakMap ephemeron value");TraceEdge(marker,&key,"proxy-preserved WeakMap ephemeron key");MOZ_ASSERT(key==p->key());// No moving}key.unsafeSet(nullptr);// Prevent destructor from running barriers.}voidtrace(JSTracer*trc)override{MOZ_ASSERT_IF(JS::CurrentThreadIsHeapBusy(),isInList());TraceNullableEdge(trc,&memberOf,"WeakMap owner");if(!Base::initialized())return;if(trc->isMarkingTracer()){MOZ_ASSERT(trc->weakMapAction()==ExpandWeakMaps);marked=true;(void)markIteratively(GCMarker::fromTracer(trc));return;}if(trc->weakMapAction()==DoNotTraceWeakMaps)return;// Trace keys only if weakMapAction() says to.if(trc->weakMapAction()==TraceWeakMapKeysValues){for(Enume(*this);!e.empty();e.popFront())TraceEdge(trc,&e.front().mutableKey(),"WeakMap entry key");}// Always trace all values (unless weakMapAction() is// DoNotTraceWeakMaps).for(Ranger=Base::all();!r.empty();r.popFront())TraceEdge(trc,&r.front().value(),"WeakMap entry value");}protected:staticvoidaddWeakEntry(GCMarker*marker,JS::GCCellPtrkey,gc::WeakMarkablemarkable){Zone*zone=key.asCell()->asTenured().zone();autop=zone->gcWeakKeys().get(key);if(p){gc::WeakEntryVector&weakEntries=p->value;if(!weakEntries.append(Move(markable)))marker->abortLinearWeakMarking();}else{gc::WeakEntryVectorweakEntries;MOZ_ALWAYS_TRUE(weakEntries.append(Move(markable)));if(!zone->gcWeakKeys().put(JS::GCCellPtr(key),Move(weakEntries)))marker->abortLinearWeakMarking();}}boolmarkIteratively(GCMarker*marker)override{MOZ_ASSERT(marked);boolmarkedAny=false;for(Enume(*this);!e.empty();e.popFront()){// If the entry is live, ensure its key and value are marked.boolkeyIsMarked=gc::IsMarked(marker->runtime(),&e.front().mutableKey());if(!keyIsMarked&&keyNeedsMark(e.front().key())){TraceEdge(marker,&e.front().mutableKey(),"proxy-preserved WeakMap entry key");keyIsMarked=true;markedAny=true;}if(keyIsMarked){if(!gc::IsMarked(marker->runtime(),&e.front().value())){TraceEdge(marker,&e.front().value(),"WeakMap entry value");markedAny=true;}}elseif(marker->isWeakMarkingTracer()){// Entry is not yet known to be live. Record this weakmap and// the lookup key in the list of weak keys. Also record the// delegate, if any, because marking the delegate also marks// the entry.JS::GCCellPtrweakKey(extractUnbarriered(e.front().key()));gc::WeakMarkablemarkable(this,weakKey);addWeakEntry(marker,weakKey,markable);if(JSObject*delegate=getDelegate(e.front().key()))addWeakEntry(marker,JS::GCCellPtr(delegate),markable);}}returnmarkedAny;}JSObject*getDelegate(JSObject*key)const{JSWeakmapKeyDelegateOpop=key->getClass()->extWeakmapKeyDelegateOp();if(!op)returnnullptr;JSObject*obj=op(key);if(!obj)returnnullptr;MOZ_ASSERT(obj->runtimeFromActiveCooperatingThread()==zone()->runtimeFromActiveCooperatingThread());returnobj;}JSObject*getDelegate(JSScript*script)const{returnnullptr;}private:voidexposeGCThingToActiveJS(constJS::Value&v)const{JS::ExposeValueToActiveJS(v);}voidexposeGCThingToActiveJS(JSObject*obj)const{JS::ExposeObjectToActiveJS(obj);}boolkeyNeedsMark(JSObject*key)const{JSObject*delegate=getDelegate(key);/* * Check if the delegate is marked with any color to properly handle * gray marking when the key's delegate is black and the map is gray. */returndelegate&&gc::IsMarkedUnbarriered(zone()->runtimeFromActiveCooperatingThread(),&delegate);}boolkeyNeedsMark(JSScript*script)const{returnfalse;}boolfindZoneEdges()override{// This is overridden by ObjectValueMap.returntrue;}voidsweep()override{/* Remove all entries whose keys remain unmarked. */for(Enume(*this);!e.empty();e.popFront()){if(gc::IsAboutToBeFinalized(&e.front().mutableKey()))e.removeFront();}/* * Once we've swept, all remaining edges should stay within the * known-live part of the graph. */assertEntriesNotAboutToBeFinalized();}voidfinish()override{Base::finish();}/* memberOf can be nullptr, which means that the map is not part of a JSObject. */voidtraceMappings(WeakMapTracer*tracer)override{for(Ranger=Base::all();!r.empty();r.popFront()){gc::Cell*key=gc::ToMarkable(r.front().key());gc::Cell*value=gc::ToMarkable(r.front().value());if(key&&value){tracer->trace(memberOf,JS::GCCellPtr(r.front().key().get()),JS::GCCellPtr(r.front().value().get()));}}}protected:voidassertEntriesNotAboutToBeFinalized(){#if DEBUGfor(Ranger=Base::all();!r.empty();r.popFront()){Keyk(r.front().key());MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k));MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value()));MOZ_ASSERT(k==r.front().key());}#endif}};/* WeakMap methods exposed so they can be installed in the self-hosting global. */externJSObject*InitBareWeakMapCtor(JSContext*cx,js::HandleObjectobj);externboolWeakMap_has(JSContext*cx,unsignedargc,Value*vp);externboolWeakMap_get(JSContext*cx,unsignedargc,Value*vp);externboolWeakMap_set(JSContext*cx,unsignedargc,Value*vp);externboolWeakMap_delete(JSContext*cx,unsignedargc,Value*vp);externJSObject*InitWeakMapClass(JSContext*cx,HandleObjectobj);classObjectValueMap:publicWeakMap<HeapPtr<JSObject*>,HeapPtr<Value>,MovableCellHasher<HeapPtr<JSObject*>>>{public:ObjectValueMap(JSContext*cx,JSObject*obj):WeakMap<HeapPtr<JSObject*>,HeapPtr<Value>,MovableCellHasher<HeapPtr<JSObject*>>>(cx,obj){}virtualboolfindZoneEdges();};// Generic weak map for mapping objects to other objects.classObjectWeakMap{ObjectValueMapmap;public:explicitObjectWeakMap(JSContext*cx);boolinit();JS::Zone*zone()const{returnmap.zone();}JSObject*lookup(constJSObject*obj);booladd(JSContext*cx,JSObject*obj,JSObject*target);voidclear();voidtrace(JSTracer*trc);size_tsizeOfExcludingThis(mozilla::MallocSizeOfmallocSizeOf);size_tsizeOfIncludingThis(mozilla::MallocSizeOfmallocSizeOf){returnmallocSizeOf(this)+sizeOfExcludingThis(mallocSizeOf);}#ifdef JSGC_HASH_TABLE_CHECKSvoidcheckAfterMovingGC();#endif};}/* namespace js */namespaceJS{template<>structDeletePolicy<js::ObjectValueMap>:publicjs::GCManagedDeletePolicy<js::ObjectValueMap>{};}/* namespace JS */#endif /* jsweakmap_h */